﻿namespace Microsoft.ExternalResearch.CreativeCommons.Common
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
    using System.Xml;
    using System.Xml.Linq;
    using System.Text.RegularExpressions;

    /// <summary>
    /// Represents the 'New license' wizard.
    /// </summary>
    public partial class NewLicenseWizard : Form
    {
        /// <summary>
        /// Base URL for the Creative Commons Web Service.
        /// </summary>
        private const string BaseCreativeCommonsApiUrl = "http://api.creativecommons.org/rest/1.5";

        /// <summary>
        /// Contains the last image requested.
        /// </summary>
        private string lastImageUrl;

        /// <summary>
        /// List of questions.
        /// </summary>
        private List<QuestionControl> questions = new List<QuestionControl>();

        /// <summary>
        /// Index of active question.
        /// </summary>
        private int activeQuestion = 0;

        /// <summary>
        /// Creates an instance of this class.
        /// </summary>
        public NewLicenseWizard()
        {
            Helper.PrepareLicenseFolders();
            this.InitializeComponent();
            this.label1.Text = String.Empty;

            this.Text = Helper.GetString("CreativeCommonsLicense");
            this.backButton.Text = Helper.GetString("Back");
            this.nextButton.Text = Helper.GetString("Next");
            this.cancelButton.Text = Helper.GetString("Cancel");
            this.warningLabel.Text = Helper.GetString("Warning");
        }

        /// <summary>
        /// Delegate for the method that displays the license image.
        /// </summary>
        /// <param name="image">Image that needs to be displayed.</param>
        private delegate void DisplayLicenseImageHandler(Image image);

        /// <summary>
        /// Gets a value indicating whether the current question is the first question, false otherwise.
        /// </summary>
        private bool FirstQuestion
        {
            get
            {
                return (0 == this.activeQuestion);
            }
        }

        /// <summary>
        /// Gets a value indicating whether the current question is the last question, false otherwise.
        /// </summary>
        private bool LastQuestion
        {
            get
            {
                // This is hard coded, but I don't have any better solution for now.
                if (this.activeQuestion == 0 && (this.questions[this.activeQuestion].Selection.Equals("publicdomain") ||this.questions[this.activeQuestion].Selection.Equals("mark")))
                {
                    return true;
                }

                return (this.questions.Count != 1 && this.activeQuestion == this.questions.Count - 1);
            }
        }

        /// <summary>
        /// Stores the ID of the license which was not returned using the Creative Commons API web.
        /// </summary>
        public string InvalidLicenseId { get; set; }

        /// <summary>
        /// Activates the specified question.
        /// </summary>
        /// <param name="question">Question to be activated.</param>
        private void Activate(QuestionControl question)
        {
            this.panel.Controls.Clear();
            this.panel.Controls.Add(question);
            question.Dock = DockStyle.Fill;

            this.UpdateLicenseImage();
            this.UpdateWizardNavigation();
        }

        /// <summary>
        /// Loads the license types information when the wizard is invoked.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event arguments.</param>
        private void WizardLoaded(object sender, EventArgs e)
        {
            this.LoadLicenseTypes();
        }

        /// <summary>
        /// Loads the license types information in the wizard.
        /// </summary>
        private void LoadLicenseTypes()
        {
            HttpWebRequest request = HttpWebRequest.Create(NewLicenseWizard.BaseCreativeCommonsApiUrl +
                "/?locale=" + NewLicenseWizard.Locale) as HttpWebRequest;
            request.Method = "GET";
            bool loadedFromDiskCache = false;
            XDocument doc = null;
            bool connectionavailable = Helper.ConnectionAvailable(request.RequestUri.AbsoluteUri);

            try
            {
                // If disk cache does not exists and unable to connect to Creative Commons API web, close the wizard.
                if (!File.Exists(Helper.LicenseTypesFilePath) && !connectionavailable)
                {
                    Helper.DisplayErrorMessage(Helper.GetString("ConnectionUnavailableMessage"));
                    this.Close();
                    return;
                }

                doc = new XDocument();

                // Try loading the license types information from disk cache.
                if (File.Exists(Helper.LicenseTypesFilePath))
                {
                    FileInfo fileInfo = new FileInfo(Helper.LicenseTypesFilePath);

                    // If the disk cache is fresh load the disk cache.
                    if ((fileInfo.LastWriteTime > DateTime.Now.AddMinutes(-Helper.CacheMinutes)) || !connectionavailable)
                    {
                        doc = XDocument.Load(Helper.LicenseTypesFilePath);
                        loadedFromDiskCache = true;
                    }
                }
            }
            catch (IOException)
            {
                Helper.DisplayErrorMessage(Helper.GetString("ErrorReadingDiskCacheMessage"));
                loadedFromDiskCache = false;
            }

            // Try loading the license types information from Creative Commons API web if unable to load from disk cache.
            if (!loadedFromDiskCache)
            {
                try
                {
                    // If connection is available, download the information and save to disk.
                    if (connectionavailable)
                    {
                        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                        doc = XDocument.Load(new XmlTextReader(response.GetResponseStream()));

                        if (File.Exists(Helper.LicenseTypesFilePath))
                        {
                            File.SetAttributes(Helper.LicenseTypesFilePath, FileAttributes.Normal);
                        }

                        doc.Save(Helper.LicenseTypesFilePath);
                        File.SetAttributes(Helper.LicenseTypesFilePath, FileAttributes.ReadOnly);
                    }
                }
                catch (WebException)
                {
                    // If we can't download information from CC API web, then wizard UI will not show
                    // anything and we should close it.
                    Helper.DisplayErrorMessage(Helper.GetString("ErrorDownloadingLicenseMessage"));
                    return;
                }
                catch (IOException)
                {
                    Helper.DisplayErrorMessage(Helper.GetString("ErrorSavingToDiskCacheMessage"));
                }
            }

            // Parse the information and display the license types in the UI.
            Question q = new Question();
            q.Id = String.Empty;
            q.Label = Helper.GetString("SelectLicenseType");

            foreach (XElement element in doc.Elements(XName.Get("licenses")).Elements("license"))
            {
                if (element.Attribute("id").Value == Helper.GetString("LicenseIdCC0"))
                { continue; }
                q.Options.Add(element.Attribute("id").Value, element.Value);
            }

            QuestionControl quc = new QuestionControl(q);
            quc.SelectionChanged += this.AnswerForQuestionChanged;
            this.questions.Add(quc);

            if (this.questions.Count > 0)
            {
                this.Activate(this.questions[0]);
            }
        }

        /// <summary>
        /// Loads the questions specific to the selected license.
        /// </summary>
        /// <param name="licenseId">Id of the selected license.</param>
        private void LoadLicenseQuestions(string licenseId)
        {
            // Get the licenses
            HttpWebRequest request = HttpWebRequest.Create(NewLicenseWizard.BaseCreativeCommonsApiUrl +
                "/license/" + licenseId + "/?locale=" + NewLicenseWizard.Locale) as HttpWebRequest;
            request.Method = "GET";
            XDocument doc = null;
            bool loadedFromDiskCache = false;
            string cacheFileName = Helper.LocalLicensesFolderPath + @"\" + licenseId + ".xml";
            bool connectionavailable = Helper.ConnectionAvailable(request.RequestUri.AbsoluteUri);

            try
            {
                // If disk cache does not exists and unable to connect to Creative Commons API web, close the wizard.
                if (!File.Exists(cacheFileName) && !connectionavailable)
                {
                    Helper.DisplayErrorMessage(Helper.GetString("ConnectionUnavailableMessage"));
                    return;
                }

                doc = new XDocument();

                // Try loading the license types information from disk cache.
                if (File.Exists(cacheFileName))
                {
                    FileInfo fileInfo = new FileInfo(cacheFileName);

                    // If the disk cache is fresh load the disk cache.
                    if ((fileInfo.LastWriteTime > DateTime.Now.AddMinutes(-Helper.CacheMinutes)) || !connectionavailable)
                    {
                        doc = XDocument.Load(Helper.LocalLicensesFolderPath + @"\" + licenseId + ".xml");
                        loadedFromDiskCache = true;
                    }
                }
            }
            catch (IOException)
            {
                Helper.DisplayErrorMessage(Helper.GetString("ErrorReadingDiskCacheMessage"));
                loadedFromDiskCache = false;
            }

            // Try loading the license types information from Creative Commons API web.
            if (!loadedFromDiskCache)
            {
                try
                {
                    // If connection is available, download the information and save to disk.
                    if (Helper.ConnectionAvailable(request.RequestUri.AbsoluteUri))
                    {
                        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                        doc = XDocument.Load(new XmlTextReader(response.GetResponseStream()));

                        if (File.Exists(cacheFileName))
                        {
                            File.SetAttributes(cacheFileName, FileAttributes.Normal);
                        }

                        doc.Save(cacheFileName);
                        File.SetAttributes(cacheFileName, FileAttributes.ReadOnly);
                    }
                }
                catch (WebException)
                {
                    // If we can't download information from CC API web, then wizard UI will not show
                    // anything and we should close it.
                    Helper.DisplayErrorMessage(Helper.GetString("ErrorDownloadingLicenseMessage"));
                    return;
                }
                catch (IOException)
                {
                    Helper.DisplayErrorMessage(Helper.GetString("ErrorSavingToDiskCacheMessage"));
                }
            }

            // Parse the information and display the questions in the UI.
            foreach (XElement field in doc.Elements(XName.Get("licenseclass")).Elements("field"))
            {
                Question q = new Question();

                q.Id = field.Attribute("id").Value;
                q.Label = field.Element("label").Value;
                q.Description = field.Element("description").Value;

                // regular expression to catch all the URLs in string output
                Regex regx = new Regex("http://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&amp;\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?", RegexOptions.IgnoreCase);
                MatchCollection matche = regx.Matches(q.Description);

                //Remove the HTML and replace with URL if URL is found in the description
                if (matche.Count > 0)
                {
                    string oldString = "<a href=\"http://wiki.creativecommons.org/Frequently_Asked_Questions#Should_I_choose_an_international_license_or_a_ported_license.3F\">";
                    string newString = matche[0].Value;
                    q.Description = q.Description.Replace(oldString.Trim(), "(" + newString + ") ");
                    q.Description = q.Description.Replace("</a>", "");

                }

                foreach (XElement option in field.Elements("enum"))
                {
                    if (option.Attribute("id").Value == Helper.GetString("LicenseIdCC0"))
                    { continue; }
                    q.Options.Add(option.Attribute("id").Value, option.Element("label").Value);
                }

                QuestionControl quc = new QuestionControl(q);
                quc.SelectionChanged += this.AnswerForQuestionChanged;

                this.questions.Add(quc);
            }
        }

        /// <summary>
        /// Changes the Next button to Finish and vice-versa.
        /// </summary>
        /// <param name="changeState">Specifies if the next button should be changed to finish.</param>
        private void ChangeNextToFinish(bool changeState)
        {
            this.nextButton.Text = !changeState ? Helper.GetString("Next") : Helper.GetString("Finish");
            this.nextButton.Enabled = true;
        }

        /// <summary>
        /// Enables/disables the navigation buttons in the wizard.
        /// </summary>
        private void UpdateWizardNavigation()
        {
            if (this.FirstQuestion && !this.LastQuestion)
            {
                this.ChangeNextToFinish(false);
                this.backButton.Enabled = false;
            }
            else if (this.LastQuestion)
            {
                this.ChangeNextToFinish(true);
                this.backButton.Enabled = !this.FirstQuestion;                
            }
            else
            {
                this.ChangeNextToFinish(false);
                this.backButton.Enabled = true;
            }

            // Disable "Next" button if the selected license is invalid.
            string licenseId = this.GenerateLicenseId();
            if (String.Compare(licenseId, this.InvalidLicenseId, StringComparison.OrdinalIgnoreCase) == 0)
            {
                this.nextButton.Enabled = false;
            }
            else
            {
                this.nextButton.Enabled = true;
            }
        }

        /// <summary>
        /// Loads the next available question and activates it.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event arguments.</param>
        private void NextQuestion(object sender, EventArgs e)
        {
            if (this.nextButton.Text.Equals(Helper.GetString("Finish")))
            {
                if (this.CreateLicense())
                {
                    this.Close();
                }

                return;
            }

            if (this.activeQuestion == 0)
            {
                this.questions.RemoveRange(1, this.questions.Count - 1);
                this.LoadLicenseQuestions(this.questions[this.activeQuestion].Selection);
            }

            if (!this.LastQuestion)
            {
                this.Activate(this.questions[++this.activeQuestion]);
            }
        }

        /// <summary>
        /// Activates the previous question.
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event arguments.</param>
        private void PreviousQuestion(object sender, EventArgs e)
        {
            this.Activate(this.questions[--this.activeQuestion]);
        }

        /// <summary>
        /// Downloads the license information from the Creative Commons API web and stores it to a local file.
        /// If the license information is already present on the local machine, it uses the existing information.
        /// </summary>
        /// <param name="licenseId">Id of the license.</param>
        /// <returns>XDocument object containing the license information.</returns>
        private XDocument GetLicense(string licenseId)
        {
            XDocument doc = null;

            StringBuilder sb = new StringBuilder(NewLicenseWizard.BaseCreativeCommonsApiUrl + "/license/" + this.questions[0].Selection + "/get?locale=" + NewLicenseWizard.Locale + "&");

            if (!this.questions[0].Selection.Equals(licenseId))
            {
                sb.Append(licenseId);
            }

            StringBuilder folderPath = new StringBuilder();
            folderPath.AppendFormat("{0}/{1}", Helper.LocalLicensesFolderPath, licenseId);
            string dir = folderPath.ToString();

            if (Directory.Exists(dir))
            {
                string strDoc = Path.Combine(dir, Helper.LicenseFileName);
                if (File.Exists(strDoc))
                {
                    XDocument document = XDocument.Load(strDoc);
                    return document;
                }
            }

            HttpWebRequest request = HttpWebRequest.Create(sb.ToString()) as HttpWebRequest;
            request.Method = "GET";

            if (!Helper.ConnectionAvailable(request.RequestUri.AbsoluteUri))
            {
                return null;
            }

            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                if (response != null && response.StatusCode == HttpStatusCode.OK)
                {
                    Stream responseStream = response.GetResponseStream();
                    doc = XDocument.Load(new XmlTextReader(responseStream));
                }
            }
            catch (WebException)
            {
                Helper.DisplayErrorMessage(Helper.GetString("LicenseDownloadError"));
            }
            catch (XmlException)
            {
                Helper.DisplayErrorMessage(Helper.GetString("LicenseDownloadError"));
            }

            return doc;
        }

        /// <summary>
        /// Displays the provided image.
        /// </summary>
        /// <param name="image">Image to be displayed.</param>
        private void DisplayLicenseImage(Image image)
        {
            this.label1.Text = (null == image) ? string.Empty : Helper.GetString("LicenseImage");
            this.pictureCurrentLicense.Image = image;
        }

        /// <summary>
        /// Updates the license image based on the user selection.
        /// </summary>
        private void UpdateLicenseImage()
        {
            string licenseId = this.GenerateLicenseId();
            string licensePath = Path.Combine(Helper.LocalLicensesFolderPath, licenseId);
            
            this.InvalidLicenseId = String.Empty;
            
            Image img = null;
            string strLicImageFile = Path.Combine(licensePath, Helper.LicenseImageFileName);
            // Check if the license image is already present on the disk cache under the license folder.
            if (File.Exists(Path.Combine(licensePath, Helper.LicenseFileName)) || File.Exists(strLicImageFile))
            {
                try
                {
                    img = Helper.GetImageFromFile(strLicImageFile);
                }
                catch (FileNotFoundException)
                {
                    // Ignore if the icon file is missing.
                }

                this.DisplayLicenseImage(img);
            }

            // Check if the license image is already present on the disk cache.
            else if (File.Exists(licensePath + ".png"))
            {
                this.lastImageUrl = licensePath;
                img = Helper.GetImageFromFile(licensePath + ".png");
                this.DisplayLicenseImage(img);
            }

            // Download the license image on disk cache.
            else
            {
                XDocument license = this.GetLicense(licenseId);

                if (null == license)
                {
                    this.InvalidLicenseId = licenseId;
                    return;
                }
                string imgsrc = string.Empty;
                if (null != license.Element("result").Element("html").Element("a"))
                {
                    imgsrc = license.Element("result").Element("html").Element("a").Element("img").Attribute("src").Value;
                }
                else 
                {
                    imgsrc = license.Element("result").Element("html").Element("p").Element("a").Element("img").Attribute("src").Value;
                }
                ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.AsyncDownloadLicenseImage);
                Thread thread = new Thread(threadStart);
                thread.IsBackground = true;
                thread.Priority = ThreadPriority.Lowest;
                this.lastImageUrl = imgsrc;
                thread.Start(imgsrc + "|" + licensePath);
            }
        }

        /// <summary>
        /// Downloads and saves the the license image.
        /// </summary>
        /// <param name="parameters">A string separated by a '|'symbol. The first part is the URL
        /// of the image that needs to be downloaded. The second part contains the disk path
        /// where the downloaded file needs to be saved.</param>
        private void AsyncDownloadLicenseImage(object parameters)
        {
            string imageUrl = parameters.ToString().Split('|')[0];
            string imagePath = parameters.ToString().Split('|')[1];

            HttpWebRequest request = HttpWebRequest.Create(imageUrl) as HttpWebRequest;
            request.Method = "GET";
            Image img = null;

            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                if (response != null && response.StatusCode == HttpStatusCode.OK)
                {
                    img = Bitmap.FromStream(response.GetResponseStream());
                }
            }
            catch (WebException)
            {
                // Ignore if the License image is missing.
            }

            if (null != img)
            {
                img.Save(imagePath + ".png");
            }

            try
            {
                // There may be a situation when due to slow performance, a thread
                // started earlier finished the job after a thread started after.
                // We want to make sure that the image displayed is from the last request.
                if (this.lastImageUrl == imageUrl)
                {
                    this.Invoke(new DisplayLicenseImageHandler(this.DisplayLicenseImage), new object[] { img });
                }
            }
            catch (ObjectDisposedException)
            {
                // we might run into a situation when the background thread finished downloading the
                // image only after the wizard was closed/disposed. We need to ignore this situation.
                // Since, the license image is saved from the picturebox in the wizard UI, 
                // when this exception occurs, it states that the picturebox in the wizard UI,
                // was surely null and the license image did not save. So, we need to save the 
                // license image to the appropriate folder here.
                // This might result in a IOException if the license folder does not exists, which
                // would be a real exception and should not be handled.
                img.Save(Path.Combine(imagePath, Helper.LicenseImageFileName));
            }
        }

        /// <summary>
        /// Generates the Id for the license based on the questions available for the license.
        /// </summary>
        /// <returns>Id of the license.</returns>
        private string GenerateLicenseId()
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i <= this.activeQuestion; i++)
            {
                if (String.IsNullOrEmpty(this.questions[i].Question.Id))
                {
                    sb.Append(this.questions[i].Selection);
                }
                else
                {
                    sb.AppendFormat("{0}={1}", this.questions[i].Question.Id, this.questions[i].Selection);
                }

                if (i < this.questions.Count - 1)
                {
                    sb.Append("&");
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Updates the wizard UI, image for the selected license and enables or disables the buttons on the wizard.
        /// </summary>
        private void AnswerForQuestionChanged(object sender, EventArgs e)
        {
            // If the choice is changed on first screen/question, remove all questions except the first one.
            if (this.activeQuestion == 0 && this.questions.Count > 1)
            {
                this.questions.RemoveRange(1, this.questions.Count - 1);
            }

            this.UpdateLicenseImage();
            this.UpdateWizardNavigation();
        }

        /// <summary>
        /// When the wizard finishes, saves the license information to the local disk.
        /// </summary>
        /// <returns>true if the license is created, false otherwise.</returns>
        private bool CreateLicense()
        {
            string licenseId = this.GenerateLicenseId();
            XDocument license = this.GetLicense(licenseId);

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{0}/{1}", Helper.LocalLicensesFolderPath, licenseId);
            string dir = sb.ToString();

            if (Directory.Exists(dir))
            {
                MessageBox.Show(Helper.GetString("LicenseAlreadyExistMessage"), Helper.GetString("Information"), MessageBoxButtons.OK, MessageBoxIcon.Information,
                    MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
                return false;
            }

            if (null == license)
            {
                Helper.DisplayErrorMessage(Helper.GetString("ConnectionUnavailableMessage"));
                return false;
            }

            Directory.CreateDirectory(dir);
            File.SetAttributes(dir, FileAttributes.Hidden);
            string strFilename = Path.Combine(dir, Helper.LicenseFileName);
            license.Save(strFilename);

            if (null != this.pictureCurrentLicense.Image)
            {
                this.pictureCurrentLicense.Image.Save(Path.Combine(dir, Helper.LicenseImageFileName));
            }

            return true;
        }


        /// <summary>
        /// The locale string to be used for a request against the CC REST API
        /// </summary>
        private static string Locale
        {
            get { return CultureInfo.CurrentCulture.Name.Replace('-', '_'); }
        }
    }
}
